﻿
#include "Configuration.h"
#include "Factory.h"
#include "Mesh.h"
#include "CommandLine.h"

#include <gtest/gtest.h>

struct SomeStruct
{
  int _v = -123;
};

std::string str = R"(
[Parallel]
  type = cuda1 
  num_threads = 8
[]


[Mesh]
  type = FileMesh
  file = 'f6.exo'
  boundary_name = 'far wall syms'
  boundary_id = '1 2 3'
[]

[BCs]
  [./wall]
    type = FarField
    boundary = 'farfield'
  [../]

  [./far]
    type = Wall
    boundary='far wall syms'
  [../]
[]

[System]
  type = PassFlow
  mach = 0.3
[]

[Executioner]
   type = Transient
   dt = 2e-05
   num_steps = 10000
   end_time = 20
[]

[Outputs]
  type = VTKOutput
  file = f6
  interval = 1000
  binary = true
  out_initial = false
[]
)";

int argc = 8;
char* argv[] = { "exe", "-i",          "input.i", "--n-threads",
                 "4",   "--mesh-only", "mach=4",  "boundary=far wall sym" };

TEST(Configuration, hand_code)
{
  Configuration cfg;
  EXPECT_FALSE(cfg.HaveVariable("a"));

  cfg.Set<int>("a", 1);
  EXPECT_TRUE(cfg.HaveVariable("a"));
  EXPECT_EQ(cfg.Get<int>("a"), 1);
  EXPECT_EQ(cfg.Get<int>("a", 23), 1);
  EXPECT_EQ(cfg.Get<bool>("use_some", true), true);

  SomeStruct* app = new SomeStruct;
  cfg.Set<SomeStruct*>("_app", app);
  EXPECT_EQ(cfg.Get<SomeStruct*>("_app"), app);
}

TEST(Configuration, stringstream)
{
  Configuration cfg;
  std::stringstream ss;
  ss << str;
  cfg.Set(ss);

  EXPECT_TRUE(cfg.HaveVariable("Parallel/type"));
  EXPECT_TRUE(cfg.HaveVariable("BCs/far/boundary"));

  cfg.SetSection("Mesh/");
  EXPECT_EQ(cfg.Get<std::string>("file"), std::string{ "f6.exo" });
  EXPECT_EQ(cfg.Get<int>("uniform_refine", 1), 1);

  cfg.SetSection("BCs/far/");
  std::vector<std::string> bnds;
  bnds = cfg.Get<std::vector<std::string>>("boundary");
  EXPECT_EQ(bnds.size(), 3);
  EXPECT_EQ(bnds[0], std::string{ "far" });
  EXPECT_EQ(bnds[1], std::string{ "wall" });
  EXPECT_EQ(bnds[2], std::string{ "syms" });

  cfg.SetSection("BCs");
  EXPECT_TRUE(cfg.HaveVariable("far/boundary"));
  cfg.SetSection("BCs/");
  EXPECT_TRUE(cfg.HaveVariable("far/boundary"));

  SomeStruct* app = new SomeStruct;
  cfg.Set<SomeStruct*>("_app", app);
  EXPECT_EQ(cfg.Get<SomeStruct*>("_app"), app);

  EXPECT_FALSE(cfg.HaveVariable("a"));

  cfg.Set<int>("a", 1);
  EXPECT_TRUE(cfg.HaveVariable("a"));
  EXPECT_EQ(cfg.Get<int>("a"), 1);
  EXPECT_EQ(cfg.Get<int>("a", 23), 1);
  EXPECT_EQ(cfg.Get<bool>("use_some", true), true);
}

TEST(Configuration, operator)
{
  Configuration cfg1;
  std::stringstream ss;
  ss << str;
  cfg1.Set(ss);

  SomeStruct* app = new SomeStruct;
  cfg1.Set<SomeStruct*>("_app", app);
  cfg1.Set<int>("a", 1);

  Configuration cfg = cfg1;

  EXPECT_TRUE(cfg.HaveVariable("Parallel/type"));
  EXPECT_TRUE(cfg.HaveVariable("BCs/far/boundary"));

  cfg.SetSection("Mesh/");
  EXPECT_EQ(cfg.Get<std::string>("file"), std::string{ "f6.exo" });
  EXPECT_EQ(cfg.Get<int>("uniform_refine", 1), 1);

  cfg.SetSection("BCs/far/");
  std::vector<std::string> bnds;
  bnds = cfg.Get<std::vector<std::string>>("boundary");
  EXPECT_EQ(bnds.size(), 3);
  EXPECT_EQ(bnds[0], std::string{ "far" });
  EXPECT_EQ(bnds[1], std::string{ "wall" });
  EXPECT_EQ(bnds[2], std::string{ "syms" });

  std::vector<int> no_exist = cfg.Get<std::vector<int>>("no_exist");
  EXPECT_EQ(no_exist.size(), 0);

  cfg.SetSection("BCs");
  EXPECT_TRUE(cfg.HaveVariable("far/boundary"));
  cfg.SetSection("BCs/");
  EXPECT_TRUE(cfg.HaveVariable("far/boundary"));


  EXPECT_EQ(cfg.Get<SomeStruct*>("_app"), app);
  EXPECT_TRUE(cfg.HaveVariable("a"));
  EXPECT_EQ(cfg.Get<int>("a"), 1);
  EXPECT_EQ(cfg.Get<int>("a", 23), 1);
  EXPECT_EQ(cfg.Get<bool>("use_some", true), true);
}

TEST(GetPot, commandline)
{
  GetPot command_line;

  command_line.parse_command_line(argc, argv);
  command_line.print();

  EXPECT_TRUE(command_line.search("exe"));
  EXPECT_TRUE(command_line.search("-i"));
  EXPECT_TRUE(command_line.next<std::string>("") == "input.i");

  EXPECT_TRUE(command_line.search("input.i"));
  EXPECT_TRUE(command_line.search("--mesh-only"));
  EXPECT_TRUE(command_line.search("--n-threads"));
  EXPECT_TRUE(command_line.next<int>(1) == 4);
  EXPECT_EQ(command_line.follow(1, "--n-threads"), 4);
  EXPECT_TRUE(command_line.follow(std::string{}, "-i") == "input.i");

  EXPECT_TRUE(command_line.have_variable("mach"));
  EXPECT_TRUE(command_line.vector_variable_size("mach") == 1);

  EXPECT_EQ(command_line("mach", 1), 4);
  EXPECT_EQ(command_line("reynolds", 1), 1);

  EXPECT_STREQ(command_line[0], "exe");
  EXPECT_TRUE(command_line.get(1, std::string{}) == "-i");
  EXPECT_TRUE(command_line.get<int>(4, 0) == 4);

  EXPECT_TRUE(command_line.have_variable("boundary"));
  EXPECT_EQ(command_line.vector_variable_size("boundary"), 3);
  EXPECT_EQ(command_line("boundary", std::string{}, 0), "far");
  EXPECT_EQ(command_line("boundary", std::string{}, 1), "wall");
  EXPECT_EQ(command_line("boundary", std::string{}, 2), "sym");
}

TEST(CommandLine, commandline)
{
  CommandLine command_line(argc, argv);
  //command_line.Set(argc, argv);

  EXPECT_TRUE(command_line.HaveArgv("exe"));
  EXPECT_TRUE(command_line.HaveArgv("-i"));
  EXPECT_TRUE(command_line.HaveArgv("input.i"));
  EXPECT_TRUE(command_line.HaveArgv("--mesh-only"));
  EXPECT_TRUE(command_line.HaveArgv("--n-threads"));
  EXPECT_EQ(command_line.Follow<int>("--n-threads"), 4);
  EXPECT_EQ(command_line.Follow("-value", 101), 101);
  EXPECT_TRUE(command_line.Follow<std::string>("-i") == "input.i");

  EXPECT_TRUE(command_line.HaveVariable("mach"));
  EXPECT_FLOAT_EQ(command_line.Get<float>("mach"), 4.0f);
  EXPECT_FLOAT_EQ(command_line.Get<float>("mach", 0.31234), 4.0f);
  EXPECT_FLOAT_EQ(command_line.Get<float>("reynolds", 1023.45), 1023.45f);
  EXPECT_EQ(command_line.VariableSize("mach"), 1);
  EXPECT_EQ(command_line.VariableSize("no_exit"), 0);

  EXPECT_EQ(command_line.VariableSize("boundary"), 3);

  std::vector<std::string> bnds = command_line.Get<std::vector<std::string>>("boundary");
  EXPECT_EQ(bnds.size(), 3);
  EXPECT_EQ(bnds[0], std::string{ "far" });
  EXPECT_EQ(bnds[1], std::string{ "wall" });
  EXPECT_EQ(bnds[2], std::string{ "sym" });

  EXPECT_EQ(command_line.VariableSize("no_exist"), 0);
  std::vector<int> no_exist = command_line.Get<std::vector<int>>("no_exist");
  EXPECT_EQ(no_exist.size(), 0);

  command_line.Usage() = "hell world";
  EXPECT_EQ(command_line.Usage(), std::string{ "hell world" });
}

TEST(GetpotParser, stringstream)
{
  std::stringstream ss;
  ss << str;
  GetpotParser parser;
  parser.Set(ss);

  auto& pot = parser.Getpot();
  pot.print();

  for (auto& i : pot.get_section_names())
  {
    std::cout << i << std::endl;
  }

  std::cout << pot("Parallel/type", std::string{}) << std::endl;
  std::cout << pot.follow(std::string{ "123" }, "Parallel/num_threads=8") << std::endl;

  EXPECT_TRUE(parser.HaveArgv("Parallel/type=cuda1"));
  EXPECT_TRUE(parser.HaveVariable("Parallel/type"));
  EXPECT_TRUE(parser.HaveVariable("BCs/far/boundary"));

  EXPECT_TRUE(parser.VariableSize("BCs/far/boundary") == 3);

  parser.SetSection("BCs/far/");
  std::vector<std::string> bnds;
  bnds = parser.Get<std::vector<std::string>>("boundary");
  EXPECT_EQ(bnds.size(), 3);
  EXPECT_EQ(bnds[0], std::string{ "far" });
  EXPECT_EQ(bnds[1], std::string{ "wall" });
  EXPECT_EQ(bnds[2], std::string{ "syms" });

  EXPECT_TRUE(is_vector<std::vector<int>>::value);
  EXPECT_TRUE(!is_vector<int>::value);
  EXPECT_TRUE(is_vector_v<std::vector<int>>);
  EXPECT_TRUE(!is_vector_v<int>);

  parser.SetSection("");

  EXPECT_TRUE(parser.HaveSection("BCs"));
  EXPECT_TRUE(parser.HaveSection("BCs/"));

  parser.SetSection("BCs");
  EXPECT_TRUE(parser.HaveVariable("far/boundary"));
  parser.SetSection("BCs/");
  EXPECT_TRUE(parser.HaveVariable("far/boundary"));
}

TEST(GetPot, set)
{
  GetPot pot(argc, argv);
  for (auto& i : pot.get_section_names())
  {
    std::cout << i << std::endl;
  }

  pot.set("mach", -1);
  EXPECT_EQ(pot("mach", 0), -1);
  pot.set("reynolds", 1.2345f);
  EXPECT_FLOAT_EQ(pot("reynolds", 1.1f), 1.2345f);
}

TEST(AnyParser, parser)
{
  AnyParser parser;
  parser.Set("a", 1);

  EXPECT_EQ(parser.Get<int>("a"), 1);
  EXPECT_EQ(parser.Get<int>("a", 2), 1);
  EXPECT_EQ(parser.Get<int>("b", 2), 2);

  SomeStruct* app = new SomeStruct;
  parser.Set<SomeStruct*>("_app", app);
  EXPECT_EQ(parser.Get<SomeStruct*>("_app"), app);

  parser.Set("boundary", std::vector<std::string>{ "far", "wall", "symm" });
  auto bnds = parser.Get<std::vector<std::string>>("boundary");
  EXPECT_EQ(bnds.size(), 3);
  EXPECT_EQ(bnds[0], std::string{ "far" });
  EXPECT_EQ(bnds[1], std::string{ "wall" });
  EXPECT_EQ(bnds[2], std::string{ "symm" });

  auto blks = parser.Get<std::vector<std::string>>("no_exist");
  EXPECT_EQ(blks.size(), 0);
}


TEST(Mesh, mesh)
{
  std::string para = R"(
    [Mesh]
      type = FileMesh
      file = 'f6.exo'
      boundary_name = 'far wall syms'
      boundary_id = '1 2 3'
    []
  )";

  Configuration cfg;
  std::stringstream ss;
  ss << para;
  cfg.Set(ss);
  cfg.SetSection("Mesh/");
  Mesh mesh(cfg);

  EXPECT_EQ(mesh._file, std::string{ "f6.exo" });
}

TEST(Factory, Register)
{
  using ObjectFactory = Factory<Object, const Configuration&>;

  ObjectFactory factory;
  factory.Register<FileMesh>("FileMesh");

  std::string para = R"(
    [Mesh]
      type = FileMesh
      file = 'f6.exo'
      boundary_name = 'far wall syms'
      boundary_id = '1 2 3'
    []
  )";

  std::stringstream ss(para);
  Configuration cfg;
  cfg.Set(ss);
  cfg.SetSection("Mesh/");

  auto type = cfg.Get<std::string>("type");
  std::shared_ptr<Mesh> mesh = factory.Make<FileMesh>(type, cfg);

  EXPECT_EQ(mesh->_file, std::string{ "f6.exo" });

  auto bnds = mesh->_bnd_names;
  EXPECT_EQ(bnds.size(), 3);
  EXPECT_EQ(bnds[0], std::string{ "far" });
  EXPECT_EQ(bnds[1], std::string{ "wall" });
  EXPECT_EQ(bnds[2], std::string{ "syms" });
}